using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Launcher;

public partial class Downloader
{
    private int _downloadIndex;
    private readonly Lock _downloadIndexLock = new();
    private readonly Lock _downloadPerSecondLock = new();
    private readonly List<Resource> _downloadList = new();
    private readonly List<Resource> _downloadFailList = new();
    private readonly List<string> _removeOriginCachedOnFinish = new();
    private readonly string _uniqueIdentification;
    private Package? _package;
    private StreamWriter? _downloadedListWriter;
    private long _downloadedSizeLastSecond;
    private ResourceInfo? _resourceInfo;
    private DownloadState _state = DownloadState.Free;
    private CancellationTokenSource? _cts;

    public DownloadState State
    {
        get => _state;
        set
        {
            var oldState = _state;
            _state = value;
            if (oldState != value)
            {
                OnStateChange?.Invoke(value);
            }
        }
    }

    public Action? OnProgress;
    public Action? OnFinish;
    public Action<string>? OnError;
    public Action<DownloadState>? OnStateChange;

    public long TotalSize { get; private set; }
    public long DownloadedSize { get; private set; }
    public long DownloadedSizePerSecond { get; private set; }
  

    public double Factor
    {
        get
        {
            if (TotalSize == 0 || DownloadedSize == 0)
            {
                return 0;
            }

            return (double)DownloadedSize / TotalSize;
        }
    }

    private Downloader(string uniqueIdentification)
    {
        _uniqueIdentification = uniqueIdentification;
    }

    public async void Start(Package package)
    {
        if (State != DownloadState.Free)
        {
            OnError?.Invoke("正在下载中，无法启动下载");
            return;
        }

        _cts = new();

        try
        {
            _package = package;
            State = DownloadState.RequestInfo;
            for (int i = 0; i < RetryTime; i++)
            {
                using ( var response = await Utils.HttpClient.GetAsync(new Uri(_package.ResourceInfoUrl)))
                {
                    try
                    {
                        response.EnsureSuccessStatusCode();
                        var json = await response.Content.ReadAsStringAsync();
                        _resourceInfo = JsonSerializer.Deserialize<ResourceInfo>(json);
                    }
                    catch (Exception e)
                    {
                        Console.Error.WriteLine(e);
                    }
                }
                
                if (_resourceInfo != null)
                {
                    break;
                }

                await Task.Delay(TimeSpan.FromMilliseconds(500));
            }

            if (_resourceInfo == null)
            {
                ErrorExitDownloading("资源信息获取失败");
                return;
            }

            if (!CollectDownloadList())
            {
                return;
            }

            State = DownloadState.Downloading;

            try
            {
                var directory = Path.Combine(DownloadTmpPath, UniqueIdentificationToFolderName(_uniqueIdentification));
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                _downloadedListWriter = File.AppendText(Path.Combine(directory, DownloadedList));
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e);
                ErrorExitDownloading("创建已下载文件列表失败");
                return;
            }

            var cancelToken = _cts.Token;
            UpdateDownloadSpeed(cancelToken);
            var processorCount = Setting.Instance.UseMultiThreadedDownloading ? Environment.ProcessorCount : 1;
            do
            {
                var driverPath = Path.GetPathRoot(Path.GetFullPath(Setting.Instance.CachePath));
                var availableFreeSpace = new DriveInfo(driverPath).AvailableFreeSpace;
                if (availableFreeSpace < _downloadList.Sum(x => x.Size))
                {
                    await _downloadedListWriter.DisposeAsync();
                    _downloadedListWriter = null;
                    ErrorExitDownloading("当前硬盘可用空间不足，无法下载");
                    return;
                }
                var tasks = new List<Task>();
                for (int i = 0; i < processorCount && i < _downloadList.Count; i++)
                {
                    tasks.Add(Worker(cancelToken));
                }

                await Task.WhenAll(tasks);

                if (cancelToken.IsCancellationRequested)
                {
                    ErrorExitDownloading("已取消下载");
                    return;
                }
                
                _downloadList.Clear();
                lock (_downloadFailList)
                {
                    _downloadList.AddRange(_downloadFailList);
                    _downloadFailList.Clear();
                }
                lock (_downloadIndexLock)
                {
                    _downloadIndex = 0;
                }
            } while (_downloadList.Count > 0);
            
            // 下载的文件从缓存中移动到正式目录
            var cachePath = Path.Combine(Setting.Instance.CachePath, UniqueIdentificationToFolderName(_uniqueIdentification));
            var tmpPath = Path.Combine(DownloadTmpPath, UniqueIdentificationToFolderName(_uniqueIdentification));
            try
            {
                if (_downloadedListWriter != null)
                {
                    await _downloadedListWriter.DisposeAsync();
                    _downloadedListWriter = null;
                }
                
                File.Delete(Path.Combine(tmpPath,DownloadedList));
                if (!Directory.Exists(cachePath))
                {
                    Directory.CreateDirectory(cachePath);
                }
                await File.WriteAllTextAsync(Path.Combine(cachePath,ResourceInfo),JsonSerializer.Serialize(_resourceInfo));
                Utils.MoveDirectory(tmpPath,cachePath);
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e);
                ErrorExitDownloading("移动临时文件到缓存目录失败");
                return;
            }
            
            // 删除废弃的文件
            foreach (var filePath in _removeOriginCachedOnFinish)
            {
                try
                {
                    var fullPath = Path.Combine(cachePath, filePath);
                    if (File.Exists(fullPath))
                    {
                        File.Delete(fullPath);
                    }
                }
                catch (Exception e)
                {
                    Console.Error.WriteLine(e);
                }
            }

            if (Directory.Exists(tmpPath))
            {
                Directory.Delete(tmpPath,true);
            }
            
            OnFinish?.Invoke();
            State = DownloadState.Free;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            ErrorExitDownloading("未知错误");
        }
    }

    public void Pause()
    {
        if (State == DownloadState.Freezing)
        {
            State = DownloadState.Downloading;
        }
        else if(State == DownloadState.Downloading)
        {
            State = DownloadState.Freezing;
        }
    }

    public void Stop()
    {
        if (_cts != null)
        {
            _cts.Cancel();
            _cts = null;
        }

        if (_downloadedListWriter != null)
        {
            _downloadedListWriter.Dispose();
            _downloadedListWriter = null;
        }
    }

    private bool CollectDownloadList()
    {
        _downloadList.Clear();
        _removeOriginCachedOnFinish.Clear();
        var cacheResourceInfoPath = Path.Combine(Setting.Instance.CachePath,
            UniqueIdentificationToFolderName(_uniqueIdentification), ResourceInfo);
        var allResources = new Dictionary<string, Resource>();
        foreach (var resource in _resourceInfo.Resources)
        {
            if (!allResources.TryAdd(resource.Path, resource))
            {
                ErrorExitDownloading($"资源清单中存在重复资源{resource.Path}");
                return false;
            }
        }

        ResourceInfo? downloadedResources = null;
        if (File.Exists(cacheResourceInfoPath))
        {
            try
            {
                downloadedResources = JsonSerializer.Deserialize<ResourceInfo>(File.ReadAllText(cacheResourceInfoPath));
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e);
                ErrorExitDownloading("读取已缓存资源信息失败");
                return false;
            }
        }

        if (downloadedResources != null)
        {
            foreach (var resource in downloadedResources.Resources)
            {
                if (allResources.TryGetValue(resource.Path, out var r))
                {
                    if (r.Hash == resource.Hash)
                    {
                        allResources.Remove(resource.Path);
                    }
                }
                else
                {
                    _removeOriginCachedOnFinish.Add(resource.Path);
                }
            }
        }

        var downloadedListPath = Path.Combine(DownloadTmpPath, UniqueIdentificationToFolderName(_uniqueIdentification), DownloadedList);
        if (File.Exists(downloadedListPath))
        {
            try
            {
                var allDownloadedResources = File.ReadAllLines(downloadedListPath);
                var removeTmpList = new List<string>();
                var saveTmpList = new List<string>();
                foreach (var line in allDownloadedResources)
                {
                    var split = line.Split(':');
                    if (split.Length >= 2)
                    {
                        var url = split[0];
                        var hash = split[1];
                        if (allResources.TryGetValue(url, out var r) && r.Hash == hash)
                        {
                            allResources.Remove(url);
                            saveTmpList.Add(line);
                        }
                        else
                        {
                            removeTmpList.Add(url);
                        }
                    }
                }

                try
                {
                    File.WriteAllLines(downloadedListPath, saveTmpList);
                }
                catch (Exception e)
                {
                    Console.Error.WriteLine(e);
                    ErrorExitDownloading("已下载资源清单被占用，无法写入");
                    return false;
                }

                var cacheRootPath = Path.Combine(DownloadTmpPath, UniqueIdentificationToFolderName(_uniqueIdentification));
                foreach (var path in removeTmpList)
                {
                    try
                    {
                        File.Delete(Path.Combine(cacheRootPath, path));
                    }
                    catch (Exception e)
                    {
                        Console.Error.WriteLine(e);
                    }
                }
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e);
                ErrorExitDownloading("读取已下载资源信息失败");
                return false;
            }
        }

        _downloadList.AddRange(allResources.Values);
        DownloadedSize = 0;
        TotalSize = _downloadList.Sum(item => item.Size);
        return true;
    }

    private async Task Worker(CancellationToken token)
    {
        if (_package == null)
        {
            return;
        }

        var rootPath = _package.ResourceRootUrl.EndsWith("/")
            ? new Uri(_package.ResourceRootUrl) 
            : new Uri(_package.ResourceRootUrl + "/");
        while (true)
        {
            int index = 0;
            lock (_downloadIndexLock)
            {
                index = _downloadIndex;
                _downloadIndex++;
            }

            if (index >= _downloadList.Count)
            {
                break;
            }

            var resource = _downloadList[index];
            try
            {
                var targetPath = Path.Combine(DownloadTmpPath, UniqueIdentificationToFolderName(_uniqueIdentification), resource.Path);
                var root = Path.GetDirectoryName(targetPath);
                if (!string.IsNullOrWhiteSpace(root) && !Directory.Exists(root))
                {
                    Directory.CreateDirectory(root);
                }
                using (var response = await Utils.HttpClient.GetAsync(new Uri(rootPath,resource.Path), HttpCompletionOption.ResponseHeadersRead, token))
                {
                    response.EnsureSuccessStatusCode();
                    await using (var streamToReadFrom = await response.Content.ReadAsStreamAsync(token))
                    {
                        await using (var streamToWriteTo = File.Create(targetPath))
                        {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = await streamToReadFrom.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
                            {
                                while (State != DownloadState.Downloading)
                                {
                                    await Task.Delay(TimeSpan.FromMilliseconds(100), token);
                                }
                                await streamToWriteTo.WriteAsync(buffer.AsMemory(0, bytesRead), token);
                                lock (_downloadPerSecondLock)
                                {
                                    DownloadedSize += bytesRead;
                                    _downloadedSizeLastSecond += bytesRead;
                                    Console.WriteLine();
                                }
                            }
                        }
                    }
                }

                if (_downloadedListWriter != null)
                {
                    lock (_downloadedListWriter)
                    {
                        _downloadedListWriter.WriteLine($"{resource.Path}:{resource.Hash}");
                    }
                }
            }
            catch (OperationCanceledException canceledException)
            {
                
            }
            catch (Exception e)
            {
                lock (_downloadFailList)
                {
                    _downloadFailList.Add(resource);
                }
                Console.Error.WriteLine(e);
            }
        }
    }

    private async void UpdateDownloadSpeed(CancellationToken token)
    {
        try
        {
            while (State != DownloadState.Free)
            {
                lock (_downloadPerSecondLock)
                {
                    DownloadedSizePerSecond = _downloadedSizeLastSecond;
                    _downloadedSizeLastSecond = 0;
                }
                await Task.Delay(TimeSpan.FromSeconds(1), token);
                OnProgress?.Invoke();
            }
        }
        catch (OperationCanceledException canceledException)
        {
        }
        catch (Exception e)
        {
            Console.Error.WriteLine(e);
        }
    }

    private void ErrorExitDownloading(string msg)
    {
        _downloadedListWriter?.Dispose();
        _downloadedListWriter = null;
        OnError?.Invoke(msg);
        State = DownloadState.Free;
    }
}